package com.wh.hotel.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.wh.hotel.cache.HotelPriceStrategyCache;
import com.wh.hotel.convert.HotelStructConvert;
import com.wh.hotel.dao.HotelPricingStrategyDao;
import com.wh.hotel.dao.HotelRoomTypeCalendarDao;
import com.wh.hotel.dao.HotelRoomTypeRelationDao;
import com.wh.hotel.enums.YesNoEnum;
import com.wh.hotel.enums.base.HotelOperateTypeEnum;
import com.wh.hotel.enums.strategy.PriceGapTypeEnum;
import com.wh.hotel.enums.strategy.PriceStrategyTypeEnum;
import com.wh.hotel.enums.strategy.SuitTypeEnum;
import com.wh.hotel.enums.strategy.TimeTypeEnum;
import com.wh.hotel.model.HotelPricingStrategy;
import com.wh.hotel.model.HotelRoomTypeCalendar;
import com.wh.hotel.model.HotelRoomTypeRelation;
import com.wh.hotel.param.HotelQueryRoomParam;
import com.wh.hotel.param.StatusChangeParam;
import com.wh.hotel.po.*;
import com.wh.hotel.service.HotelPriceStrategyService;
import com.wh.hotel.service.strategy.PriceStrategyFactory;
import com.wh.hotel.util.DateWhUtil;
import com.wh.hotel.util.PageResultUtil;
import com.wh.hotel.vo.HotelPriceStrategyVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author luweije
 */
@Slf4j
@Service
public class HotelPriceStrategyServiceImpl implements HotelPriceStrategyService {

    @Resource
    private HotelPricingStrategyDao hotelPricingStrategyDao;

    @Resource
    private HotelStructConvert hotelStructConvert;

    @Resource
    private HotelRoomTypeRelationDao hotelRoomTypeRelationDao;

    @Resource
    private HotelPriceStrategyCache hotelPriceStrategyCache;

    @Resource
    private PriceStrategyFactory priceStrategyFactory;

    @Resource
    private HotelRoomTypeCalendarDao hotelRoomTypeCalendarDao;

    @Resource
    private HotelPriceStrategyService hotelPriceStrategyService;

    @Override
    public HotelPricingStrategy savePriceStrategy(HotelPriceStrategyVo hotelPriceStrategyVo) {
        Preconditions.checkNotNull(PriceStrategyTypeEnum.parse(hotelPriceStrategyVo.getStrategyType()), "策略类型枚举不合规");
        StrategySuitRangePo suitRangePo = hotelPriceStrategyVo.getSuitRangePo();
        Preconditions.checkNotNull(SuitTypeEnum.parse(suitRangePo.getSuitRangeType()), "适用类型枚举不合规");
        Preconditions.checkArgument(CollectionUtils.isNotEmpty(suitRangePo.getSuitHotelRoomPoList()), "适用酒店-房型不能空");
        ValidTimePo validTimePo = hotelPriceStrategyVo.getValidTimePo();
        if (validTimePo.getEndTime() != null) {
            validTimePo.setEndTime(DateWhUtil.addDays(validTimePo.getEndTime(), 1));
        }
        TimeTypeEnum timeTypeEnum = TimeTypeEnum.parse(validTimePo.getTimeType());
        Preconditions.checkNotNull(timeTypeEnum, "生效时间范围枚举不合规");
        switch (timeTypeEnum) {
            case ABSOLUTE_TIME:
                Preconditions.checkNotNull(validTimePo.getStartTime(), "生效开始时间不能空");
                break;
            case PRE_WEEK:
                Preconditions.checkArgument(CollectionUtils.isNotEmpty(validTimePo.getWeekdayList()), "生效周几列表不能空");
                for (Integer dayOfWeek : validTimePo.getWeekdayList()) {
                    Preconditions.checkArgument(dayOfWeek >= 1 && dayOfWeek <= 7, "周几的数值不合法");
                }
                break;
        }
        PriceChangeVo priceChangeVo = hotelPriceStrategyVo.getPriceChangeVo();
        Preconditions.checkNotNull(PriceGapTypeEnum.parse(priceChangeVo.getPriceGapType()), "枚举不合规");
        HotelPricingStrategy hotelPricingStrategy = hotelStructConvert.convertToHotelPricingStrategy(hotelPriceStrategyVo);
        hotelPricingStrategy.setStrategyStatus(YesNoEnum.NO.getCode());
        hotelPricingStrategy.setId(hotelPriceStrategyVo.getId());
        if (hotelPriceStrategyVo.getId() != null) {
            // 修改
            Preconditions.checkArgument(hotelPricingStrategyDao.update(hotelPricingStrategy), "更新定价策略失败");
            return hotelPricingStrategy;
        }
        Preconditions.checkArgument(hotelPricingStrategyDao.insert(hotelPricingStrategy), "插入定价策略失败");
        return hotelPricingStrategy;
    }

    @Override
    public Boolean updateStrategyStatus(StatusChangeParam statusChangeParam) {
        HotelOperateTypeEnum operateTypeEnum = HotelOperateTypeEnum.parse(statusChangeParam.getOperateType());
        return hotelPricingStrategyDao.updateStrategyStatus(statusChangeParam.getId(), operateTypeEnum);
    }

    @Override
    public IPage<HotelPriceStrategyVo> list(HotelPriceStrategyVo hotelPriceStrategyVo) {
        IPage<HotelPricingStrategy> hotelPricingStrategyPage = hotelPricingStrategyDao.selectPageByCondition(hotelPriceStrategyVo);
        IPage<HotelPriceStrategyVo> result = new Page<>();
        PageResultUtil.copyPageInfo(hotelPricingStrategyPage, result);
        result.setRecords(hotelPricingStrategyPage.getRecords().stream()
                .map(strategy -> {
                    HotelPriceStrategyVo strategyVo = hotelStructConvert.convertToHotelPriceStrategyVo(strategy);
                    return convertToHotelPriceStrategyVoView(strategyVo);
                })
                .collect(Collectors.toList())
        );
        return result;
    }

    @Override
    public HotelPriceStrategyVo detail(HotelPriceStrategyVo strategyVo) {
        HotelPricingStrategy hotelPricingStrategy = hotelPricingStrategyDao.selectById(strategyVo.getId());
        return convertToHotelPriceStrategyVoView(hotelStructConvert.convertToHotelPriceStrategyVo(hotelPricingStrategy));
    }

    private HotelPriceStrategyVo convertToHotelPriceStrategyVoView(HotelPriceStrategyVo strategyVo) {
        ValidTimePo validTimePo = strategyVo.getValidTimePo();
        validTimePo.setEndTime(DateWhUtil.addDays(validTimePo.getEndTime(), -1));
        return strategyVo;
    }

    @Override
    public List<PriceHitStrategyPo> hitPriceStrategy(SuitHotelRoomPo suitHotelRoomPo, List<Date> calendar,
                                                     PriceStrategyTypeEnum strategyTypeEnum) {
        initSuitParam(suitHotelRoomPo);
        if (CollectionUtils.isEmpty(suitHotelRoomPo.getRoomTypeIdList())) {
            return Collections.emptyList();
        }
        if (CollectionUtils.isEmpty(calendar)) {
            return Collections.emptyList();
        }
        List<HotelPriceStrategyVo> orgStrategyList = hotelPriceStrategyCache.getNormalAllByType(strategyTypeEnum);
        HotelQueryRoomParam param = new HotelQueryRoomParam();
        param.setHotelId(suitHotelRoomPo.getHotelId());
        List<PriceHitStrategyPo> result = Lists.newArrayList();
        Map<Long, HotelRoomTypeRelation> roomTypeRelationMap = hotelRoomTypeRelationDao.selectByRoomTypeIdList(suitHotelRoomPo.getRoomTypeIdList())
                .stream()
                .collect(Collectors.toMap(HotelRoomTypeRelation::getRoomTypeId, a -> a, (k1, k2) -> k1));
        for (Long roomTypeId : suitHotelRoomPo.getRoomTypeIdList()) {
            param.setRoomTypeId(roomTypeId);
            // 适用酒店房型过滤
            List<HotelPriceStrategyVo> filterStrategyList = priceStrategyFactory.filterBySuitRange(orgStrategyList, param);
            // 适用生效时间过滤
            HotelRoomTypeRelation hotelRoomTypeRelation = roomTypeRelationMap.get(roomTypeId);
            List<PriceHitStrategyPo> priceHitStrategyPoList = Lists.newArrayList();
            for (Date cur : calendar) {
                List<HotelPriceStrategyVo> finalStrategyList = priceStrategyFactory.filterByTimeType(filterStrategyList, cur);
                PriceHitStrategyPo hitStrategyPo = new PriceHitStrategyPo();
                hitStrategyPo.setSellDate(cur);
                if (CollectionUtils.isEmpty(finalStrategyList)) {
                    priceHitStrategyPoList.add(hitStrategyPo);
                    continue;
                }
                // todo 策略选择规则
                HotelPriceStrategyVo strategyVo = findBestStrategy(finalStrategyList);
                hitStrategyPo.setStrategyId(strategyVo.getId());
                priceHitStrategyPoList.add(hitStrategyPo);
            }
            result.addAll(priceHitStrategyPoList);
            // 价格计算
            for (PriceHitStrategyPo hitStrategyPo : priceHitStrategyPoList) {
                hitStrategyPo.setHotelId(suitHotelRoomPo.getHotelId());
                hitStrategyPo.setRoomTypeId(roomTypeId);
                HotelPriceStrategyVo strategyVo = hotelPriceStrategyCache.getByStrategyId(hitStrategyPo.getStrategyId());
                hitStrategyPo.setOrgPrice(priceStrategyFactory.calculateOrgPrice(hotelRoomTypeRelation.getBasePrice(), strategyVo));
            }
        }
        return result;
    }

    private HotelPriceStrategyVo findBestStrategy(List<HotelPriceStrategyVo> finalStrategyList) {
        long time = 0;
        HotelPriceStrategyVo result = null;
        for (HotelPriceStrategyVo strategyVo : finalStrategyList) {
            long dbTime = strategyVo.getCreateDt().getTime();
            if (dbTime > time) {
                time = dbTime;
                result = strategyVo;
            }
        }
        return result;
    }

    private void initSuitParam(SuitHotelRoomPo suitHotelRoomPo) {
        if (CollectionUtils.isNotEmpty(suitHotelRoomPo.getRoomTypeIdList())) {
            return;
        }
        List<HotelRoomTypeRelation> relationList = hotelRoomTypeRelationDao.selectAllByHotelId(suitHotelRoomPo.getHotelId(), YesNoEnum.YES);
        if (CollectionUtils.isEmpty(relationList)) {
            return;
        }
        suitHotelRoomPo.setRoomTypeIdList(relationList.stream().map(HotelRoomTypeRelation::getRoomTypeId).collect(Collectors.toList()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePriceCalendar(List<PriceHitStrategyPo> priceHitStrategyPoList) {
        for (PriceHitStrategyPo hitStrategyPo : priceHitStrategyPoList) {
            if (hitStrategyPo.getStrategyId() == null) {
                hotelRoomTypeCalendarDao.delete(hitStrategyPo.getRoomTypeId(), hitStrategyPo.getSellDate());
                continue;
            }
            hotelRoomTypeCalendarDao.insertOrUpdate(hitStrategyPo);
        }
    }

    @Override
    public void hitAndUpdatePriceStrategy(SuitHotelRoomPo suitHotelRoomPo, List<Date> calendar, PriceStrategyTypeEnum strategyTypeEnum) {
        List<PriceHitStrategyPo> priceHitStrategyPoList = hotelPriceStrategyService.hitPriceStrategy(suitHotelRoomPo, calendar, strategyTypeEnum);
        hotelPriceStrategyService.updatePriceCalendar(priceHitStrategyPoList);
    }
}
